package vnc.server.rfb.messages;

import java.awt.AWTException;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Timer;
import java.util.TimerTask;

import vnc.diagnostics.Stopwatch;
import vnc.server.Main;
import vnc.server.rfb.ScreenHandler;

import com.mar.framecompress.EncoderRegistry;
import com.mar.framecompress.Encoding;
import com.mar.framecompress.IEncoder;

public class FramebufferUpdateMessage extends Message {

	private static final int RECT_WIDTH = 32;
	private static final int TIMER_INTERVAL = 20;

	private void setTimer() {
		new Timer().schedule(new TimerTask() {

			@Override
			public void run() {

				try {
					Main.update.process();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}, TIMER_INTERVAL);

	}

	private static final ByteBuffer imageRectBuffer = ByteBuffer.allocate(2048);
	private static final ByteBuffer imageRectCompressedBuffer = ByteBuffer
			.allocate(2048);
	private static final IEncoder encoder = EncoderRegistry.getInstance().get(
			Encoding.DEFAULT);

	public FramebufferUpdateMessage(Socket clientSocket) {
		super(clientSocket, ProcessMode.write);
		try {
			this.screenHandler = new ScreenHandler(width, height);
		} catch (AWTException e) {
			throw new IllegalStateException("Could not init screen handler!", e);
		}
	}

	int[] lastCapture = new int[width * height];

	protected void SendRectangle() {

	}

	byte getRepresentation(int argb) {
		int b = (argb & 0xC0) >>> ((8 - 2) - 6);
		int g = (argb & 0xE000) >>> ((8 - 3 + 8) - 3);
		int r = (argb & 0xE00000) >>> ((8 - 3 + 8 + 8) - 0);
		byte value = (byte) (r | g | b);
		return value;
	}

	int getFromCapture(int width, int[] capture, int x, int y) {
		return capture[width * y + x];
	}

	final static int width = 1024;
	final static int height = 768;
	final ScreenHandler screenHandler;

	@Override
	public void doWriteMessage(Object readData) throws IOException {
		System.out.println(this.toString());

		long sent = 0;
		
		int rectWidth = RECT_WIDTH;

		// Counting changed rects

		Stopwatch sw = new Stopwatch();

		long start = System.currentTimeMillis();
		int[] capture = screenHandler.createScreenCapture(0, 0);

		sw.logrestart("capturescreen");

		int rectsToChange = 0;

		for (int j = 0; j < height / rectWidth; j++) {
			for (int i = 0; i < width / rectWidth; i++) {

				Boolean needsInvalidation = false;

				if (lastCapture != null) {
					for (int k = 0; k < rectWidth; k++) {
						if (!needsInvalidation) {
							for (int l = 0; l < rectWidth; l++) {
								if (getFromCapture(width, capture, i
										* rectWidth + l, j * rectWidth + k) != getFromCapture(
										width, lastCapture, i * rectWidth + l,
										j * rectWidth + k)) {
									needsInvalidation = true;
									break;
								}
							}
						} else {
							break;
						}
					}
				} else {
					needsInvalidation = true;
				}

				if (needsInvalidation) {
					rectsToChange++;
				}

			}
		}

		sw.logrestart("first walk to detect rects");

		if (rectsToChange == 0) { 
			setTimer();
			return;
		}

//		dout.writeByte(0);
		jout.writeS8(0);
		// padding
//		dout.writeByte(1);
		jout.writeS8(1);
		// Number of rectangles
//		dout.writeShort(rectsToChange);
		jout.writeS16(rectsToChange);

		// sending rects

		for (int j = 0; j < height / rectWidth; j++) {
			for (int i = 0; i < width / rectWidth; i++) {

				Boolean needsInvalidation = false;

				if (lastCapture != null) {
					for (int k = 0; k < rectWidth; k++) {
						if (!needsInvalidation) {
							for (int l = 0; l < rectWidth; l++) {
								if (getFromCapture(width, capture, i
										* rectWidth + l, j * rectWidth + k) != getFromCapture(
										width, lastCapture, i * rectWidth + l,
										j * rectWidth + k)) {
									needsInvalidation = true;
									break;
								}
							}
						} else {
							break;
						}
					}
				} else {
					needsInvalidation = true;
				}

				if (needsInvalidation) {

					// x,y,width, height
//					dout.writeShort(i * rectWidth);
					jout.writeS16(i * rectWidth);
//					dout.writeShort(j * rectWidth);
					jout.writeS16(j * rectWidth);
					// send white
					//
//					dout.writeShort(rectWidth);
					jout.writeS16(rectWidth);
//					dout.writeShort(rectWidth);
					jout.writeS16(rectWidth);
					// raw
//					dout.writeInt(0);
					jout.writeS32(0);
//					dout.flush();

					for (int k = 0; k < rectWidth; k++) {
						for (int l = 0; l < rectWidth; l++) {
							// jout.writeU8(getRepresentation(getFromCapture(width,
							// capture, i * rectWidth + l, j * rectWidth +
							// k)));
							imageRectBuffer
									.put(getRepresentation(getFromCapture(
											width, capture, i * rectWidth + l,
											j * rectWidth + k)));
						}
					}
					// jout.flush();
					imageRectBuffer.flip();
					Stopwatch encodeSw = new Stopwatch();
					encoder.encode(imageRectBuffer, imageRectCompressedBuffer);
					// encodeSw.logrestart("encoding");
					jout.writeS32(imageRectCompressedBuffer.limit());
					jout.writeBytes(imageRectCompressedBuffer.array(), 0,
							imageRectCompressedBuffer.limit());
					sent += imageRectCompressedBuffer.limit();
					imageRectBuffer.clear();
					imageRectCompressedBuffer.clear();

				}

			}
		}
		//System.arraycopy(capture, 0, lastCapture, 0, lastCapture.length);
		lastCapture = capture;
		sw.logrestart("second walk to send results");
		jout.flush();
		bufferedOutputStream.flush();
		sw.logrestart("flush");
		System.out.println("Total bytes sent: " + sent);
		setTimer();

	}

}
